feat: resolve latest patch for a major.minor version#293
Conversation
Allow the version input and version-file entries to be a major.minor value like
3.14 / v3.14, resolving to the newest available patch (e.g. v3.14.4).
Resolution probes the download host directly: sequential HEAD requests for
helm-v{major}.{minor}.{n} against downloadBaseURL, taking the highest that
returns 200. Works with any file-serving mirror and needs no token or extra
dependency, and cannot resolve to a version that is not downloadable. Only
major.minor.n URLs are probed, so prereleases are never considered.
- Add isMajorMinorShaped, helmPatchExists, and resolveLatestPatchVersion.
- Walk stops after 3 consecutive 404s (look-ahead) to tolerate a skipped patch
number, with a 100-probe safety cap; hitting the cap throws instead of
returning a bogus version.
- Wire resolution into run() and relax getVersionFromToolVersionsFile to accept
a major.minor value.
There was a problem hiding this comment.
Pull request overview
This PR adds support for specifying Helm as a major.minor input (e.g., 3.14 / v3.14) and resolves it to the newest downloadable patch release by probing the configured download host.
Changes:
- Add
isMajorMinorShaped(),helmPatchExists(), andresolveLatestPatchVersion()to resolvemajor.minortov{major}.{minor}.{latestPatch}via sequential host probes. - Wire
major.minorresolution intorun()and relax.tool-versionsparsing to acceptmajor.minor. - Add unit tests covering parsing/shape checks and patch-resolution behavior.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| src/run.ts | Adds major.minor detection and resolves it to the latest patch via HEAD probes against the download host. |
| src/run.test.ts | Adds tests for major.minor parsing and latest-patch resolution logic (including skipped patches and error paths). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Extend the major.minor latest-patch resolution to accept wildcard patch syntax (`3.12.x`, `v3.12.x`, `3.12.*`), matching the syntax requested in Azure#109. The following smaller fixes were made based on Copilot review feedback: - helmPatchExists now treats only 404 as "patch absent"; any other non-2xx status (403/405/429/5xx) and network errors are thrown, so rate-limiting, outages, or a host that disallows HEAD can no longer be misread as a missing patch (which could yield a stale version or a false "No Helm releases found"). - Clarify the version-file validation error to reflect that both a full version and a major.minor value are accepted, with concrete examples. - Tests: use vi.spyOn(globalThis, 'fetch') instead of vi.stubGlobal so restoreAllMocks() reliably restores fetch and the mock never leaks across tests; add coverage for wildcards and the non-404 throw path.
|
instead of interating through every patch version sequentially, consider a prefix listing ex: |
Thanks for this David. I did consider this approach but I later had to abandon it. enumerating versions relies on Azure Blob Storage's specific listing API, which only works if the download host is Azure storage. A custom mirror (S3, Artifactory, nginx, etc.) usually doesn't support it — so it would break for mirror users |
| // Matches a major.minor version with an optional leading 'v' and either no | ||
| // patch component or a wildcard patch ('.x' / '.*'), e.g. '3.14', 'v3.14', | ||
| // '3.14.x', 'v3.14.*'. | ||
| const majorMinorShape = /^v?\d+\.\d+(?:\.[x*])?$/ |
There was a problem hiding this comment.
if we are going to use regex here, we should use the one from semver https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string
There was a problem hiding this comment.
Hi David, thanks for the feedback! In this case I don't think the semver.org regex fits, since it only validates complete X.Y.Z strings and has no notation for the partial versions this pattern is meant to match (e.g. 3.14, 3.14.x, 3.14.*). Swapping it in here would actually break the latest-patch feature, which depends on accepting an incomplete version string to resolve the newest patch.
That said, the semver.org regex is a great fit for our full version check, so I will adopt it for the (isSemVerShape)regex instead
| version: string | ||
| ): Promise<boolean> { | ||
| const url = getHelmDownloadURL(baseURL, version) | ||
| const response = await fetch(url, {method: 'HEAD'}) |
There was a problem hiding this comment.
do mirrors typically support HEAD method?
There was a problem hiding this comment.
Yes they do. HEAD is a universal standard method that comes for free with plain file serving which is the exact capability a mirror must support to serve downloads at all
i see. what about detecting if it's an azure blob store to then use this optimization? |
Nice suggestion! However, I don't think upfront detection is worth the overhead (and hostname sniffing would actually miss get.helm.sh, since it's a CNAME over Azure Blob). Instead, I've added the listing as an optimization with a fallback: the action first tries to enumerate from whatever download host the binaries live at, and if that returns no valid listing it falls back to the iterative probe. Same-host enumeration means we never resolve a version the host can't serve, and it works on any mirror. |
**Listing fast-path (with fallback).** Following the discussion about enumeration, `resolveLatestPatchVersion` now first tries a single Azure Blob container-listing request against the download host (which `get.helm.sh` backs), and only falls back to the sequential HEAD-probe walk if the host doesn't return a valid listing. Enumeration and download stay on the **same host**, so there's no risk of resolving a version the host can't serve, and it still works on any mirror. - Default host: resolution now takes **1 request** instead of ~8 probes (verified live: `3.9`→`v3.9.4`, `3.12`→`v3.12.3`, `3.14`→`v3.14.4`, `3.16`→`v3.16.4`, `2.17`→`v2.17.0`). - Non-listing hosts fall back to probing; prereleases and `.sha256` sidecars are excluded from the listing parse. **semver.org regex.** Per review feedback, `isSemVerShaped` now uses the official regex from semver.org (with an added optional leading `v` for Helm tags) instead of the hand-rolled one. Added tests for the listing fast-path, the fallback, and a non-listing response; full suite is green (`npm test`, `typecheck`, `format-check`).
This pull request adds support for specifying Helm versions as major.minor (e.g.,
3.14) or with a wildcard patch (e.g.,3.14.xor3.14.*) in addition to full semantic versions. It introduces logic to resolve these inputs to the latest available patch version by probing the download host. The tests have been expanded to cover these new behaviors, and error handling has been improved for version validation and network scenarios.Version resolution enhancements:
isMajorMinorShapedto detect major.minor and wildcard patch versions, and updated validation to accept these forms ingetVersionFromToolVersionsFile. [1] [2]resolveLatestPatchVersionto probe the download host and resolve a major.minor version (or wildcard patch) to the latest available patch version, with robust error handling for missing releases and network errors.runlogic to support resolving major.minor and wildcard patch versions before proceeding with download.Testing improvements:
vi.stubGlobaltovi.spyOn(globalThis, 'fetch')in tests to ensure reliable restoration of mocks and prevent test leakage.Error messaging:
.tool-versions, clarifying the accepted formats and providing actionable guidance.These changes improve flexibility for users specifying Helm versions and ensure robust, predictable behavior across a wider range of inputs.
This PR once merges will fix #109